/* $ZEL: sis1100_autoconf_linux.c,v 1.12 2006/05/15 17:54:59 wuestner Exp $ */

/*
 * Copyright (c) 2001-2004
 * 	Matthias Drochner, Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sis1100_sc.h"

int sis1100_major = -1;

__devinitdata
struct pci_device_id sis1100_table[]={
    {
    PCI_VENDOR_FZJZEL, PCI_PRODUCT_FZJZEL_GIGALINK,
    PCI_ANY_ID, PCI_ANY_ID,
    0, 0,
    0
    },
    { 0 }
};

MODULE_AUTHOR("Peter Wuestner <P.Wuestner@fz-juelich.de>");
MODULE_DESCRIPTION("SIS1100 PCI-VME link/interface (http://zelweb.zel.kfa-juelich.de/projects/gigalink/)");
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
MODULE_SUPPORTED_DEVICE("sis1100/sis3100/sis5100; http://www.struck.de/pcivme.htm");

#define DRIVER_VERSION "2.04"

struct sis1100_softc *sis1100_devdata[sis1100_MAXCARDS];

struct file_operations sis1100_fops = {
	.owner   = THIS_MODULE,
	.llseek  = sis1100_llseek,
	.read    = sis1100_read,
	.write   = sis1100_write,
#ifdef HAVE_UNLOCKED_IOCTL
	.unlocked_ioctl = sis1100_ioctl,
#else
	.ioctl   = sis1100_ioctl,
#endif
	.mmap    = sis1100_mmap,
        .poll    = sis1100_poll,
	.open    = sis1100_open,
	.release = sis1100_release,
#if defined (CONFIG_COMPAT) && defined (HAVE_COMPAT_IOCTL)
        .compat_ioctl = sis1100_ioctl32,
#endif
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)

#define dev2pci(d) container_of(d, struct pci_dev, dev)
#define dev2sc(d) pci_get_drvdata(dev2pci(d))
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
#define SHOW_ARGS(d, a, b) (d, a, b) 
#define STORE_ARGS(d, a, b, c) (d, a, b, c) 
#else
#define SHOW_ARGS(d, a, b) (d, b) 
#define STORE_ARGS(d, a, b, c) (d, b, c) 
#endif

ssize_t
show_driver_version SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    return snprintf(buf, PAGE_SIZE, DRIVER_VERSION "\n");
}
ssize_t
show_physaddr_plx SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->plx_addr);
}
ssize_t
show_physaddr_reg SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->reg_addr);
}
ssize_t
show_physaddr_rem SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->rem_addr);
}
ssize_t
show_remote_size SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%x\n", sc->rem_size);
}
ssize_t
show_local_version SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
        sc->local_ident&0xff,
        (sc->local_ident>>8)&0xff,
        (sc->local_ident>>16)&0xff,
        (sc->local_ident>>24)&0xff);
}
ssize_t
show_remote_version SHOW_ARGS(struct device* d, struct device_attribute *attr, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    if (sc->remote_hw==sis1100_hw_invalid) {
        return snprintf(buf, PAGE_SIZE, "0 0 0 0\n");
    } else {
        return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
            sc->remote_ident&0xff,
            (sc->remote_ident>>8)&0xff,
            (sc->remote_ident>>16)&0xff,
            (sc->remote_ident>>24)&0xff);
    }
}

ssize_t
dummy_store STORE_ARGS(struct device* d, struct device_attribute *attr, const char *buf,
        size_t count)
{
    return 0;
}

DEVICE_ATTR(driver_version, 0444, show_driver_version, dummy_store);
DEVICE_ATTR(physaddr_plx, 0444, show_physaddr_plx, dummy_store);
DEVICE_ATTR(physaddr_reg, 0444, show_physaddr_reg, dummy_store);
DEVICE_ATTR(physaddr_rem, 0444, show_physaddr_rem, dummy_store);
DEVICE_ATTR(remote_size, 0444, show_remote_size, dummy_store);
DEVICE_ATTR(local_version, 0444, show_local_version, dummy_store);
DEVICE_ATTR(remote_version, 0444, show_remote_version, dummy_store);

#endif /* KERNEL_VERSION >= 2.6.0 */

void __init sis1100_print_info(void)
{
    printk(KERN_INFO "SIS1100 driver V" DRIVER_VERSION
            " (c) 11.Jun.2004 FZ Juelich\n");
}

int __devinit
sis1100_linux_init(struct pci_dev *dev)
{
        struct sis1100_softc *sc;
	int i, idx, res;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
        printk(KERN_INFO "sis1100: found %s at %s\n", dev->name, dev->slot_name);
#else
        printk(KERN_INFO "sis1100: found sis1100 at %s\n", pci_name(dev));
#endif
/*
        printk(KERN_INFO "vendor=0x%04x\n", dev->vendor);
        printk(KERN_INFO "device=0x%04x\n", dev->device);
        printk(KERN_INFO "sub_vendor=0x%04x\n", dev->subsystem_vendor);
        printk(KERN_INFO "sub_device=0x%04x\n", dev->subsystem_device);
        printk(KERN_INFO "class=%d\n", dev->class);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
        printk(KERN_INFO "name=>%s<\n", dev->name);
#else
        printk(KERN_INFO "name=>%s<\n", dev->dev.name);
#endif
        printk(KERN_INFO "slot_name=>%s<\n", dev->slot_name);
*/
        for (idx = 0; idx < sis1100_MAXCARDS; idx++) {
                if (!sis1100_devdata[idx]) break;
        }
        if (idx == sis1100_MAXCARDS) return -ENOMEM;

        sc = kmalloc(sizeof(struct sis1100_softc), GFP_KERNEL);
        if (!sc) return -ENOMEM;
        sis1100_devdata[idx] = sc;
        sc->unit = idx;

        for (i=0; i<=sis1100_MINORUTMASK; i++) sc->fdatalist[i]=0;

        init_waitqueue_head(&sc->handler_wait);
        init_waitqueue_head(&sc->local_wait);
        init_waitqueue_head(&sc->remoteirq_wait);
        init_timer(&sc->link_up_timer);
        sc->link_up_timer.function=sis1100_link_up_handler;
        sc->link_up_timer.data=(unsigned long)sc;

        init_MUTEX (&sc->sem_hw);
        init_MUTEX (&sc->sem_fdata_list);
        init_MUTEX (&sc->sem_irqinfo);
        init_MUTEX(&sc->demand_dma.sem);
        spin_lock_init(&sc->lock_intcsr);
        spin_lock_init(&sc->handlercommand.lock);
        spin_lock_init(&sc->lock_doorbell);
        spin_lock_init(&sc->lock_lemo_status);
        spin_lock_init(&sc->demand_dma.spin);
        sc->handlercommand.command=0;
        INIT_LIST_HEAD(&sc->fdata_list_head);
        init_completion(&sc->handler_completion);

        sc->demand_dma.status=dma_invalid;
        sc->plxirq_dma0_hook=0;

        sc->handler_pid=kernel_thread(sis1100_irq_thread, sc, 0);
        if (sc->handler_pid<0) {
            printk(KERN_ERR "create sis1100_irq_handler: %d\n", sc->handler_pid);
            res=sc->handler_pid;
            goto fehler_kmalloc_sc;
        }
        sc->handler_task=find_task_by_pid(sc->handler_pid);
        sc->sched_param.sched_priority=20;
        res=sched_setscheduler(sc->handler_task, SCHED_FIFO, &sc->sched_param);
        if (res!=0) {
            pINFO(sc, "sched_setscheduler: %d", res);
        }

        sc->plxmembase=0;
        sc->reg_base=0;
        sc->rem_base=0;

        sc->plx_addr = pci_resource_start(dev, 0);
        sc->plxmemlen = pci_resource_len(dev, 0);
        sc->plxmembase = ioremap_nocache(sc->plx_addr, sc->plxmemlen);
        pINFO(sc, "plx_addr=0x%08lx", sc->plx_addr);
        pINFO(sc, "mapped at %p (size=0x%x)", sc->plxmembase, sc->plxmemlen);
        if (!sc->plxmembase) {
                res=-ENOMEM;
                pERROR(sc, "can't map plx space");
                goto fehler_ioremap;
        }
        sc->reg_addr = pci_resource_start(dev, 2);
        sc->reg_size = pci_resource_len(dev, 2);
        sc->reg_base = ioremap_nocache(sc->reg_addr, sc->reg_size);
        pINFO(sc, "reg_addr=0x%08lx", sc->reg_addr);
        pINFO(sc, "mapped at %p (size=0x%x)", sc->reg_base, sc->reg_size);
        if (!sc->reg_base) {
                res=-ENOMEM;
                pERROR(sc, "can't map register space");
                goto fehler_ioremap;
        }
        sc->rem_addr = pci_resource_start(dev, 3);
        sc->rem_size = pci_resource_len(dev, 3);
        pINFO(sc, "rem_addr=0x%08lx", sc->rem_addr);
        pINFO(sc, "rem_size=0x%08lx", (unsigned long)sc->rem_size);
        do {
	    sc->rem_base = ioremap_nocache(sc->rem_addr, sc->rem_size);
            if (!sc->rem_base)
                sc->rem_size>>=1;
        } while (!sc->rem_base && sc->rem_size);
	if (sc->rem_base) {
            pINFO(sc, "rem_addr=0x%08lx", sc->rem_addr);
            pINFO(sc, "mapped at %p (size=0x%x)", sc->rem_base, sc->rem_size);
        } else {
            pWARNING(sc, "can't map remote space (size=0x%x)", sc->rem_size);
	    pWARNING(sc, "mmap not available");
            sc->rem_size=0;
        }
	res = pci_request_regions(dev, "sis1100");
	if (res)
		goto fehler_ioremap;

	res = request_irq(dev->irq, sis1100_intr, SA_SHIRQ, "sis1100", sc);
	if (res) {
            pWARNING(sc, "error installing irq");
	    goto fehler_request_regions;
        }

	sc->pcidev = dev;
	pci_set_drvdata(dev, sc);

	pci_set_master(dev);

#if 0
        sc->no_dma=0;
        /*if (!pci_set_dma_mask(dev, 0xffffffffffffffff)) {
            pWARNING(sc, "64bit DMA available (but not used yet).");
            sc->dma_dac=1;
        } else*/ if (!pci_set_dma_mask(dev, 0xffffffff)) {
            sc->dma_dac=0;
        } else {
            pWARNING(sc, "DMA not available.");
            sc->no_dma=1; /* not used yet */
        }
#endif
        if (pci_set_dma_mask(dev, 0xffffffff)) {
            pWARNING(sc, "DMA not available.");
	    goto fehler_request_irq;
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        pci_set_consistent_dma_mask(dev, 0xffffffff);
#endif

#ifndef USE_SGL
        if ((res=alloc_kiovec(1, &sc->iobuf))<0) {
                sc->iobuf=0;
                goto fehler_request_irq;
        }
#endif
        sc->descbuf.size=SGL_SIZE*sizeof(struct plx9054_dmadesc);
        sc->descbuf.cpu_addr=pci_alloc_consistent(sc->pcidev,
    	        sc->descbuf.size, &sc->descbuf.dma_handle);
        if (!sc->descbuf.cpu_addr) {
                pERROR(sc, "pci_alloc_consistent failed");
                res=-ENOMEM;
                goto fehler_alloc_kiovec;
        }
        /*mem_map_reserve(virt_to_page(sc->descbuf.cpu_addr));*/
        pINFO(sc, "descbuf.dma_handle=0x%08llx",
                (unsigned long long)sc->descbuf.dma_handle);
        /*
        pINFO(sc, "descbuf.cpu_addr=0x%08llx",
                (unsigned long long)sc->descbuf.cpu_addr);
        */


    	res=-sis1100_init(sc);
	if (res) {
	    goto fehler_alloc_descbuf;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
/* create files for sysfs */
        device_create_file(&dev->dev, &dev_attr_driver_version);
        device_create_file(&dev->dev, &dev_attr_physaddr_plx);
        device_create_file(&dev->dev, &dev_attr_physaddr_reg);
        device_create_file(&dev->dev, &dev_attr_physaddr_rem);
        device_create_file(&dev->dev, &dev_attr_remote_size);
        device_create_file(&dev->dev, &dev_attr_local_version);
        device_create_file(&dev->dev, &dev_attr_remote_version);
#endif

    	return 0;

/*fehler_sis1100_init:*/
        sis1100_done(sc);

fehler_alloc_descbuf:
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
        pci_free_consistent(sc->pcidev, sc->descbuf.size,
                sc->descbuf.cpu_addr, sc->descbuf.dma_handle);

fehler_alloc_kiovec:
#ifndef USE_SGL
        free_kiovec(1, &sc->iobuf);
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
#endif

fehler_request_irq:
        free_irq(dev->irq, sc);

fehler_request_regions:
	pci_release_regions(dev);

fehler_ioremap:
	if (sc->plxmembase) iounmap((void *)sc->plxmembase);
	if (sc->reg_base) iounmap((void *)sc->reg_base);
	if (sc->rem_base) iounmap((void *)sc->rem_base);

/*fehler_irq_thread:*/
	{
            unsigned long flags;
            spin_lock_irqsave(&sc->handlercommand.lock, flags);
            sc->handlercommand.command=handlercomm_die;
            spin_unlock_irqrestore(&sc->handlercommand.lock, flags);
            wake_up(&sc->handler_wait);
            wait_for_completion (&sc->handler_completion);
	}

fehler_kmalloc_sc:
	sis1100_devdata[sc->unit] = 0;
	kfree(sc);
	pci_set_drvdata(dev, NULL);
        if (res>=0) {
            pERROR(sc, "sis1100_linux_init: res=%d", res);
            res=-EINVAL;
        }
    	return res;
}

void __devexit
sis1100_linux_done(struct pci_dev *dev)
{
	struct sis1100_softc *sc;

	sc = pci_get_drvdata(dev);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
/* delete files from sysfs */
        device_remove_file(&dev->dev, &dev_attr_driver_version);
        device_remove_file(&dev->dev, &dev_attr_physaddr_plx);
        device_remove_file(&dev->dev, &dev_attr_physaddr_reg);
        device_remove_file(&dev->dev, &dev_attr_physaddr_rem);
        device_remove_file(&dev->dev, &dev_attr_remote_size);
        device_remove_file(&dev->dev, &dev_attr_local_version);
        device_remove_file(&dev->dev, &dev_attr_remote_version);
#endif

#ifndef USE_SGL
        free_kiovec(1, &sc->iobuf);
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
#endif
        pci_free_consistent(sc->pcidev, sc->descbuf.size,
                sc->descbuf.cpu_addr, sc->descbuf.dma_handle);

	sis1100_done(sc);
	free_irq(dev->irq, sc);
	del_timer_sync(&sc->link_up_timer);
	if (sc->handler_pid>=0) {
            unsigned long flags;
            spin_lock_irqsave(&sc->handlercommand.lock, flags);
            sc->handlercommand.command=handlercomm_die;
            spin_unlock_irqrestore(&sc->handlercommand.lock, flags);
            wake_up(&sc->handler_wait);
            wait_for_completion (&sc->handler_completion);
	}
	iounmap((void *)sc->plxmembase);
	iounmap((void *)sc->reg_base);
	iounmap((void *)sc->rem_base);
	sis1100_devdata[sc->unit] = 0;

	kfree(sc);
	pci_release_regions(dev);
	pci_set_drvdata(dev, NULL);
}

int __init
sis1100_linux_drvinit(void)
{
    sis1100_major=register_chrdev(0, "sis1100", &sis1100_fops);
    if (sis1100_major<0) {
        printk(KERN_WARNING "sis1100: unable to register device\n");
        return -EBUSY;
    }
#if defined (CONFIG_COMPAT) && !defined(HAVE_COMPAT_IOCTL)
    sis1100_ioctl32_init();
#endif

    return 0;
}

void __exit
sis1100_linux_drvdone(void)
{
    unregister_chrdev(sis1100_major, "sis1100");
#if defined (CONFIG_COMPAT) && !defined(HAVE_COMPAT_IOCTL)
    sis1100_ioctl32_exit();
#endif
}
